package rexsee.core.browser;

import java.lang.reflect.Method;

import rexsee.core.application.RexseeApplication;
import rexsee.core.browser.clazz.ActivityResult;
import rexsee.core.browser.clazz.BrowserChromeClient;
import rexsee.core.browser.clazz.BrowserClient;
import rexsee.core.browser.clazz.BrowserParent;
import rexsee.core.browser.clazz.DestroyListeners;
import rexsee.core.browser.clazz.DomainWhiteList;
import rexsee.core.browser.clazz.ErrorListeners;
import rexsee.core.browser.clazz.EventList;
import rexsee.core.browser.clazz.FileSelector;
import rexsee.core.browser.clazz.GestureListener;
import rexsee.core.browser.clazz.History;
import rexsee.core.browser.clazz.JavascriptInterfaceList;
import rexsee.core.browser.clazz.LoadResourceListeners;
import rexsee.core.browser.clazz.LoadingProgressListeners;
import rexsee.core.browser.clazz.LogListeners;
import rexsee.core.browser.clazz.PageBeforeStartListeners;
import rexsee.core.browser.clazz.PageReadyListeners;
import rexsee.core.browser.clazz.PageStartedListeners;
import rexsee.core.browser.clazz.PauseListeners;
import rexsee.core.browser.clazz.ReceivedTitleListeners;
import rexsee.core.browser.clazz.ResumeListeners;
import rexsee.core.browser.clazz.SpecialKeyListener;
import rexsee.core.browser.clazz.StopListeners;
import rexsee.core.browser.clazz.TouchEventInfo;
import rexsee.core.browser.clazz.TouchListener;
import rexsee.core.browser.clazz.WindowCreateCloseListener;
import rexsee.core.lang.RexseeLanguage;
import rexsee.core.menu.RexseeMenu;
import rexsee.core.progress.RexseeProgressCover;
import rexsee.core.progress.RexseeProgressDialog;
import rexsee.core.style.RexseeDrawable;
import rexsee.core.style.StyleSheet;
import rexsee.core.utilities.Escape;
import rexsee.core.utilities.RexseeClazz;
import rexsee.core.utilities.Utilities;
import rexsee.core.widget.Div;
import rexsee.fileSelector.MediaStoreCache;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Picture;
import android.net.Uri;
import android.os.Build;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.webkit.DownloadListener;
import android.webkit.ValueCallback;
import android.webkit.WebIconDatabase;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.Toast;

public class Browser extends WebView {

	public static final String EVENT_ONEXCEPTION = "onException";
	public static final String EVENT_ONSCROLLTOP = "onScrollTop";
	public static final String EVENT_ONSCROLLBOTTOM = "onScrollBottom";
	public static final String EVENT_ONPAUSE = "onPause";
	public static final String EVENT_ONRESUME = "onResume";
	public static final String EVENT_ONDESTROY = "onDestroy";
	public static final String EVENT_ONSCREENORIENTATIONCHANGED = "onScreenOrientationChanged";
	public static final String EVENT_ONDOUBLECLICK = "onDoubleClick";
	public static final String EVENT_ONFLING = "onFling";
	public static final String EVENT_ONLONGPRESS = "onLongPress";
	public static final String EVENT_ONTOUCHDOWN = "onTouchDown";
	public static final String EVENT_ONTOUCHUP = "onTouchUp";
	public static final String EVENT_ONTOUCHMOVE = "onTouchMove";
	public static final String EVENT_ONTOUCHCANCEL = "onTouchCancel";
	public static final String EVENT_ONANIMATIONSTART = "onAnimationStart";
	public static final String EVENT_ONANIMATIONREPEAT = "onAnimationRepeat";
	public static final String EVENT_ONANIMATIONEND = "onAnimationEnd";

	public final RexseeApplication application = new RexseeApplication(this.getContext());
	public final String EVENT_ONREXSEEREADY = "on" + application.resources.prefix.substring(0, 1).toUpperCase() + application.resources.prefix.substring(1) + "Ready";

	public final RexseeGarbageCollector garbageCollector = new RexseeGarbageCollector(this);
	public final DomainWhiteList domainWhiteList = new DomainWhiteList(this.getContext());
	public final JavascriptInterfaceList interfaceList = new JavascriptInterfaceList(this);
	public final EventList eventList = new EventList(this);
	public final TouchEventInfo touchEventInfo = new TouchEventInfo();
	public final History history = new History();
	public final FileSelector fileSelector = new FileSelector(this);
	public final SpecialKeyListener specialKeyListener = new SpecialKeyListener();

	public final ActivityResult activityResult = new ActivityResult(this.getContext());
	public final PauseListeners pauseListeners = new PauseListeners();
	public final ResumeListeners resumeListeners = new ResumeListeners();
	public final DestroyListeners destroyListeners = new DestroyListeners();
	public final LogListeners logListeners = new LogListeners();

	public final LoadingProgressListeners progressListeners = new LoadingProgressListeners();
	public final ReceivedTitleListeners titleListeners = new ReceivedTitleListeners();
	public final ErrorListeners errorListeners = new ErrorListeners();
	public final PageBeforeStartListeners pageBeforeStartListeners = new PageBeforeStartListeners();
	public final PageStartedListeners pageStartedListeners = new PageStartedListeners();
	public final StopListeners stopListeners = new StopListeners();
	public final PageReadyListeners pageReadyListeners = new PageReadyListeners();
	public final LoadResourceListeners loadResourceListeners = new LoadResourceListeners();

	public final StyleSheet styleSheet = new StyleSheet(this.getContext());
	public final RexseeUrl urlListeners = new RexseeUrl(this);
	public final RexseeBrowser function = new RexseeBrowser(this);
	public final RexseeBrowserTouchEvent touchEvent = new RexseeBrowserTouchEvent(this);
	public final RexseePrompt promptListeners = new RexseePrompt(this);
	public final RexseeProgressDialog progressDialog = new RexseeProgressDialog(this);
	public final RexseeProgressCover progressCover = new RexseeProgressCover(this);
	public final RexseePreload preload = new RexseePreload(this);
	public final RexseeMenu menu = new RexseeMenu(this);

	public WindowCreateCloseListener windowListener = null;

	public final Browser parentBrowser;
	public final Div containerDiv;
	public BrowserParent parentDialog;

	public String source = "";
	public boolean allowErrorDialog = true;
	public Intent initIntent = null;
	public boolean isLaunching = true;
	public boolean allowScrollEvent = false;

	public ValueCallback<Uri> mUploadMessage;
	public final MediaStoreCache mediaStore = new MediaStoreCache();

	public Browser(final Context context, Browser theParentBrowser, Div theParentDiv, BrowserParent theParentDialog) {

		super(context);

		parentBrowser = theParentBrowser;
		containerDiv = theParentDiv;
		parentDialog = theParentDialog;

		final int timeout = application.getTimeout();
		final Runnable timeoutRunnable = new Runnable() {
			@Override
			public void run() {
				try {
					stopLoading();
					application.askForExitOrRestart(RexseeLanguage.TITLE_ERROR, RexseeLanguage.MESSAGE_WEAK_NETWORK);
				} catch (Exception e) {
				}
			}
		};
		final Runnable cancelRunnable = new Runnable() {
			@Override
			public void run() {
				application.confirmQuit();
			}
		};
		if (parentBrowser == null && application.resources.progressCover) {
			progressCover.showWithRunnable(0, application.label + RexseeLanguage.PROGRESS_STARTING + "0%", timeout, timeoutRunnable, cancelRunnable);
		}
		progressListeners.add(new LoadingProgressListeners.LoadingProgressListener() {
			@Override
			public void run(final Context context, Browser browser, int progress) {
				if (!styleSheet.browser_progress_dialog.equalsIgnoreCase("hidden")) {
					if (progress == 100) {
						progressDialog.hide();
						progressCover.hide();
						isLaunching = false;
						function.setFocusable(true);
					} else {
						if (parentBrowser == null && isLaunching && application.resources.progressCover) {
							progressCover.showWithRunnable(progress, application.label + RexseeLanguage.PROGRESS_STARTING + progress + "%", timeout, timeoutRunnable, cancelRunnable);
						} else {
							progressDialog.showWithRunnable(RexseeLanguage.PROGRESS_ONGOING + progress + "% ", new Runnable() {
								@Override
								public void run() {
									//application.confirmQuit();
									Toast.makeText(context, RexseeLanguage.PROGRESS_STOP, Toast.LENGTH_SHORT).show();
									stopLoading();
								}
							});
						}
					}
				}
			}
		});
		errorListeners.add(new ErrorListeners.ErrorListener() {
			@Override
			public void run(Context context, Browser browser, int errorCode, String description, String failingUrl) {
				browser.history.add(failingUrl);
				function.load(application.resources.prefix + ":error");
			}
		});
		loadResourceListeners.add(new LoadResourceListeners.LoadResourceListener() {
			@Override
			public void run(Context context, Browser browser, String url) {
				Uri uri = Uri.parse(url);
				String scheme = uri.getScheme();
				if ((scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")) && !domainWhiteList.isValidUrl(url)) {
					browser.stopLoading();
					browser.application.error("Not allowed to load resource: " + url);
				}
			}
		});

		eventList.add(EVENT_ONEXCEPTION);
		eventList.add(EVENT_ONPAUSE);
		eventList.add(EVENT_ONRESUME);
		eventList.add(EVENT_ONDESTROY);
		eventList.add(EVENT_ONREXSEEREADY);
		eventList.add(EVENT_ONSCREENORIENTATIONCHANGED);
		eventList.add(EVENT_ONTOUCHDOWN);
		eventList.add(EVENT_ONTOUCHUP);
		eventList.add(EVENT_ONTOUCHMOVE);
		eventList.add(EVENT_ONTOUCHCANCEL);
		eventList.add(EVENT_ONDOUBLECLICK);
		eventList.add(EVENT_ONLONGPRESS);
		eventList.add(EVENT_ONFLING);

		eventList.add(EVENT_ONANIMATIONSTART);
		eventList.add(EVENT_ONANIMATIONREPEAT);
		eventList.add(EVENT_ONANIMATIONEND);

		eventList.add(EVENT_ONSCROLLTOP);
		eventList.add(EVENT_ONSCROLLBOTTOM);

		pauseListeners.add(new Runnable() {
			@Override
			public void run() {
				eventList.run(EVENT_ONPAUSE);
			}
		});
		resumeListeners.add(new Runnable() {
			@Override
			public void run() {
				eventList.run(EVENT_ONRESUME);
			}
		});
		destroyListeners.add(new Runnable() {
			@Override
			public void run() {
				eventList.run(EVENT_ONDESTROY);
			}
		});

		domainWhiteList.inherit();
		interfaceList.inherit();
		urlListeners.inherite();

		WebSettings websettings = this.getSettings();
		websettings.setJavaScriptEnabled(true);

		websettings.setBlockNetworkLoads(false);
		websettings.setBlockNetworkImage(false);
		websettings.setLoadsImagesAutomatically(true);

		websettings.setJavaScriptCanOpenWindowsAutomatically(false);
		websettings.setSupportMultipleWindows(false);
		if (Integer.parseInt(Build.VERSION.SDK) >= 14) {
			websettings.setLightTouchEnabled(true);
		}

		websettings.setAllowFileAccess(true);
		websettings.setDomStorageEnabled(true);

		websettings.setDatabaseEnabled(true);
		websettings.setDatabasePath(this.getContext().getDir("database", Context.MODE_PRIVATE).getPath());
		WebIconDatabase.getInstance().open(this.getContext().getDir("icons", Context.MODE_PRIVATE).getPath());

		websettings.setAppCacheMaxSize(8 * 1024 * 1024);
		String cachePath = this.getContext().getDir("cache", Context.MODE_PRIVATE).getPath();
		websettings.setAppCachePath(cachePath);
		websettings.setAppCacheEnabled(true);
		websettings.setCacheMode(WebSettings.LOAD_DEFAULT);

		try {
			//websettings.setPluginState(WebSettings.PluginState.ON); 2.1+
			Class<?> pluginState = RexseeClazz.getClass(WebSettings.class, "PluginState");
			if (pluginState != null) {
				Object ON = RexseeClazz.getEnum(pluginState, "ON");
				if (ON != null) {
					Method method = RexseeClazz.getMethod(WebSettings.class, "setPluginState");
					if (method != null) {
						method.invoke(websettings, new Object[]{ON});
					} else {
					}
				} else {
				}
			} else {
			}
		} catch (Exception e) {
		}
		if (parentBrowser != null) websettings.setUserAgentString(parentBrowser.getSettings().getUserAgentString());

		WebView.enablePlatformNotifications();

		if (application.getAutoClearCache()) this.clearCache(true);
		this.setFocusable(true);
		this.setFocusableInTouchMode(true);
		this.setLongClickable(true);

		this.setDefaultTouchListener();

		this.setPictureListener(new PictureListener() {
			@Override
			public void onNewPicture(WebView view, Picture picture) {
			}
		});
		this.setDownloadListener(new DownloadListener() {
			@Override
			public void onDownloadStart(String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
				Utilities.downloadByDefault(Browser.this, url);
			}
		});
		this.setWebViewClient(new BrowserClient(this));
		this.setWebChromeClient(new BrowserChromeClient(this));

		this.setBackgroundColor(Color.parseColor(application.resources.browserBackgroundColor));
		String browserProgressDialogVisibility = (application.resources.browserProgressDialog) ? "visible" : "hidden";
		this.setStyle("scrollbar-y:auto;browser-progress-dialog:" + browserProgressDialogVisibility + ";");

		garbageCollector.startAutoCollector();
		if (parentBrowser != null) {
			setWindowCreateCloseListener(parentBrowser.windowListener);
		}

	}

	public void setDefaultTouchListener() {
		this.setOnTouchListener(new TouchListener(this, new GestureDetector(new GestureListener(this))));
	}
	public void setTouchListener(GestureDetector detector) {
		this.setOnTouchListener(new TouchListener(this, detector));
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			if (specialKeyListener.backEnabled) {
				if (specialKeyListener.back_DownRunnable != null) {
					specialKeyListener.back_DownRunnable.run();
				} else {
					function.goBack();
				}
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_MENU) {
			if (specialKeyListener.menuEnabled) {
				if (specialKeyListener.menu_DownRunnable != null) {
					specialKeyListener.menu_DownRunnable.run();
				} else {
					menu.openOptionsMenu();
				}
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
			if (specialKeyListener.searchEnabled && specialKeyListener.search_DownRunnable != null) {
				specialKeyListener.search_DownRunnable.run();
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_FOCUS) {
			if (specialKeyListener.focusEnabled && specialKeyListener.focus_DownRunnable != null) {
				specialKeyListener.focus_DownRunnable.run();
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
			if (specialKeyListener.volumeEnabled && specialKeyListener.volumeUp_DownRunnable != null) {
				specialKeyListener.volumeUp_DownRunnable.run();
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
			if (specialKeyListener.volumeEnabled && specialKeyListener.volumeDown_DownRunnable != null) {
				specialKeyListener.volumeDown_DownRunnable.run();
				return true;
			} else return false;
		} else {
			return super.onKeyDown(keyCode, event);
		}
	}
	@Override
	public boolean onKeyUp(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK) {
			if (specialKeyListener.backEnabled) {
				if (specialKeyListener.back_UpRunnable != null) {
					specialKeyListener.back_UpRunnable.run();
				}
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_MENU) {
			if (specialKeyListener.menuEnabled) {
				if (specialKeyListener.menu_UpRunnable != null) {
					specialKeyListener.menu_UpRunnable.run();
				}
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_SEARCH) {
			if (specialKeyListener.searchEnabled) {
				if (specialKeyListener.search_UpRunnable != null) {
					specialKeyListener.search_UpRunnable.run();
				}
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_FOCUS) {
			if (specialKeyListener.focusEnabled) {
				if (specialKeyListener.focus_UpRunnable != null) {
					specialKeyListener.focus_UpRunnable.run();
				}
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
			if (specialKeyListener.volumeEnabled) {
				if (specialKeyListener.volumeUp_UpRunnable != null) {
					specialKeyListener.volumeUp_UpRunnable.run();
				}
				return true;
			} else return false;
		} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
			if (specialKeyListener.volumeEnabled) {
				if (specialKeyListener.volumeDown_UpRunnable != null) {
					specialKeyListener.volumeDown_UpRunnable.run();
				}
				return true;
			} else return false;
		} else {
			return super.onKeyUp(keyCode, event);
		}
	}
	@Override
	public void onScrollChanged(int l, int t, int oldl, int oldt) {
		super.onScrollChanged(l, t, oldl, oldt);
		if (oldl != l || oldt != t) history.setCurrentItemScroll(l, t);
		if (!allowScrollEvent) return;
		int contentHeigth = (int) (getContentHeight() * application.screenDensity);
		int oldBottom = oldt + getHeight() - contentHeigth;
		int bottom = t + getHeight() - contentHeigth;
		if (t == 0 && oldt != 0) {
			eventList.run(EVENT_ONSCROLLTOP);
		} else if (bottom <= 10 && bottom >= 0 && !(oldBottom <= 10 && oldBottom >= 0) && t > oldt) {
			eventList.run(EVENT_ONSCROLLBOTTOM);
		}
	}
	@Override
	public int getSolidColor() {
		return Color.parseColor(styleSheet.fading_edge_y_color);
	}

	public void setStyle(final String style) {

		styleSheet.parseStyle(style);
		if (styleSheet.browser_zoom_button.equalsIgnoreCase("visible")) {
			this.getSettings().setBuiltInZoomControls(true);
			this.invokeZoomPicker();
		} else {
			this.getSettings().setBuiltInZoomControls(false);
		}
		this.setBackgroundDrawable(RexseeDrawable.getDrawable(this, styleSheet.background_color));
		if (styleSheet.background_color.equalsIgnoreCase("transparent")) this.setBackgroundColor(Color.TRANSPARENT);

		String fadingEdgeLength = styleSheet.fading_edge_y_length;
		this.setHorizontalFadingEdgeEnabled(true);
		this.setVerticalFadingEdgeEnabled(true);
		this.setFadingEdgeLength(Integer.parseInt(fadingEdgeLength.toLowerCase().replaceAll("px", "")));

		String scrollbar = styleSheet.scrollbar_y;
		if (scrollbar.equalsIgnoreCase("hidden")) {
			this.setHorizontalScrollBarEnabled(false);
			this.setVerticalScrollBarEnabled(false);
		} else {
			this.setHorizontalScrollBarEnabled(true);
			this.setVerticalScrollBarEnabled(true);
			this.setScrollBarStyle(styleSheet.getScrollbarYStyle());
			this.setScrollbarFadingEnabled((scrollbar.equalsIgnoreCase("visible")) ? false : true);
		}

	}
	public void setParentDialog(BrowserParent theParentDialog) {
		parentDialog = theParentDialog;
	}

	public void setWindowCreateCloseListener(WindowCreateCloseListener listener) {
		WebSettings websettings = this.getSettings();
		if (listener == null) {
			websettings.setJavaScriptCanOpenWindowsAutomatically(false);
			websettings.setSupportMultipleWindows(false);
		} else {
			websettings.setJavaScriptCanOpenWindowsAutomatically(true);
			websettings.setSupportMultipleWindows(true);
		}
		windowListener = listener;
	}

	public int findAndHighlight(String find) {
		requestFocus();
		try {
			int rtn = findAll(find);
			Method m = WebView.class.getMethod("setFindIsUp", Boolean.TYPE);
			m.invoke(this, true);
			return rtn;
		} catch (Exception e) {
			return 0;
		}
	}
	public void clearFind() {
		try {
			Method m = WebView.class.getMethod("setFindIsUp", Boolean.TYPE);
			m.invoke(this, false);
		} catch (Exception e) {
		}
	}
	public void findNext() {
		findNext(true);
	}
	public void findPrevious() {
		findNext(false);
	}

	public void exception(String interfaceName, Exception e) {
		exception(interfaceName, e.toString() + "/" + e.getLocalizedMessage());
	}
	public void exception(String interfaceName, String message) {
		eventList.run(EVENT_ONEXCEPTION, new String[]{interfaceName, Escape.escape(message)});
		if (logListeners != null) logListeners.run(getContext(), this, LogListeners.LOG_TYPE_EXCEPTION, interfaceName + ":" + message);
	}

	@Override
	public boolean performLongClick() {
		return false;
	}

	public void selectionDone() {
	}

	public void enableSelectionMode() {
		try {
			Method m = WebView.class.getDeclaredMethod("setUpSelect");
			m.invoke(this);
		} catch (Exception e) {
		}
	}
	public String disableSelectionMode() {
		String rtn;
		try {
			Method r = WebView.class.getDeclaredMethod("getSelection");
			rtn = String.valueOf(r.invoke(this));
		} catch (Exception e) {
			rtn = "";
		}
		try {
			Method m = WebView.class.getDeclaredMethod("notifySelectDialogDismissed");
			m.invoke(this);
		} catch (Exception e) {
		}
		return rtn;
	}

	public void onDestroy() {
		stopLoading();
		destroyListeners.run();
		styleSheet.destroy();
		try {
			destroyDrawingCache();
			freeMemory();
			clearCache(false);
			destroy();
		} catch (Exception e) {
		}
	}

}
